home *** CD-ROM | disk | FTP | other *** search
/ Experimental BBS Explossion 3 / Experimental BBS Explossion III.iso / games / nhak_src.zip / MAINTAIN.OVL < prev    next >
Text File  |  1993-03-16  |  18KB  |  337 lines

  1.             Maintaining PC NetHack
  2.                ========================
  3.                Last revision: 1990may27
  4.  
  5. The installation of the system of overlay management that currently
  6. brings full-featured NetHack to the IBM PC and compatibles has
  7. introduced a number of arcanities into the source code of the
  8. programme, and unfortunately running afoul of these intricacies can
  9. result (as we ourselves have discovered) in the most bizarre and
  10. strangely inexplicable dysfunctional manifestations, aka sick bugs.
  11.  
  12. This document is required reading for anyone making substantive
  13. changes to NetHack for the PC or embarking upon a revision of its
  14. overlay structure.
  15.  
  16.  
  17. 1. The overlay manager
  18. ----------------------
  19. NetHack is by now a fairly large programme (in excess of 800
  20. kilobytes), and in order to compile it for the PC (which typically
  21. has little more than 500k of available memory) it was necessary to
  22. rely on the technique of _overlaying_, whereby not all the
  23. programme is resident in memory at the same time, segments of the
  24. programme being loaded and discarded as they are needed. Unlike
  25. traditional candidates for the overlaying strategy, however, NetHack
  26. does not exhibit strongly phased behaviour; although much of the code
  27. is not being used at any one moment, there is comparatively little
  28. code that can be confidently said not to be related to or potentially
  29. necessary for the immediate progress of the game.
  30.     Furthermore we wished to develop an overlaying strategy that
  31. did _not_ involve intimate knowledge of the operation of the
  32. programme (since NetHack is an international team effort, and few
  33. people have a good feeling for the totality of the code structure),
  34. and which would not require substantive changes to the source code,
  35. impacting on its maintainability and portability.
  36.     It turned out to be impossible to satisfy these goals with
  37. tools that are widely available at the time of writing, and so we
  38. undertook to write our own overlay manager (compatible with
  39. Microsoft's, but more in concert with NetHack's particular needs).
  40. The result is called ovlmgr.asm and is documented in the file
  41. ovlmgr.doc. You would probably be well advised to read at least the
  42. less technical parts of that file now.
  43.  
  44.  
  45. 2. The trampoli mechanism
  46. -------------------------
  47. One of the difficulties with using overlays for C (particularly
  48. Microsoft C) is that while common C programming practise places heavy
  49. reliance on function pointers, Microsoft's overlay linker is unable
  50. to resolve calls through pointers to functions that are in remote
  51. overlays. Nor, unfortunately, does it choose to report such failures;
  52. rather, it generates calls into (what often turns out to be in the
  53. case of our nonstandard overlay manager) the deepest of space. This
  54. can result in truly strange behaviour on the part of your programme -
  55. including bugs that come and go in as close to a random pattern as
  56. you are ever likely to see.
  57.     Other than the creative use of pattern-matching utilities
  58. such as grep to locate the offending calls, there is unfortunately no
  59. advice we can offer in tracking down these bugs. Once they have been
  60. isolated, however, they can be remedied straightforwardly.
  61.  
  62. In order for the linker not to screw up on a pointered function call
  63. it is (to simplify an actually rather complicated situation)
  64. necessary that the function called be located in the ROOT "overlay",
  65. and thus not be subject to swapping. Rather than linking the full
  66. text of every pointered function into the root, however, it suffices
  67. to place a "trampoline" function there which performs a direct call
  68. to the "real" function that does the work, in whatever overlay it
  69. might naturally reside in. Due to a not-quite-accident of the
  70. behaviour of the C preprocessor (it was originally intended to make
  71. possible functions whose address can be taken but which expand inline
  72. as macros where possible, a not unrelated function), it turns out to
  73. be possible to arrange for this without major change to the C source
  74. code - and without serious impact on the performance of "regular"
  75. calls to the same functions.
  76.  
  77. The C preprocessor's expansion of a macro with parameters is triggered
  78. by an encounter with the macro name immediately followed by an open
  79. parenthesis. If the name is found, but it is not followed by a
  80. parenthesis, the macro is not matched and no expansion takes place.
  81. At the same time it may be noted that (unless someone has been oddly
  82. strange and enclosed a function name in quite unneeded parentheses!),
  83. a function name is typically followed by an open parenthesis if, and
  84. only if, it is being declared, defined or invoked; if its address is
  85. being taken it will necessarily be followed by some other token.
  86. Furthermore (except in the unfortunate case of the ill-conceived
  87. new-style ANSI declaration of a function that takes no parameters) it
  88. will be observed that the number of parameters to a call of the
  89. function (assuming that this number is fixed; if not, I grant, we
  90. have a problem) is the same in all these contexts. This implies that
  91. if all the modules of a programme are uniformly processed in the
  92. context of a macro definition such as
  93.  
  94.     #define zook(a,b) plenk(a,b)
  95.  
  96. and assuming that all functions named zook() take exactly two
  97. arguments, then the resulting programme will be completely identical
  98. to the original (without this definition) except that the link
  99. map will report the existence of the function plenk() in place of
  100. zook() -- UNLESS there was a place in the programme where the address
  101. of zook was taken. In that case, the linker would report an
  102. unresolved external reference for that symbol.
  103.     That unresolved reference is, of course, precisely what we
  104. need; if in another source file (one that did not see the macro
  105. definition) we placed the function declaration
  106.  
  107.     some_t zook(this_t a, that_t b)
  108.       { extern some_t plenk(this_t, that_t);
  109.         return plenk(a, b);
  110.       }
  111.  
  112. this would both satisfy the unresolved reference and restore the
  113. original semantics of the programme (even including pointer
  114. comparison!) -- while providing us with precisely the kind of
  115. "trampoline" module that we need to circumvent the problem with the
  116. linker.
  117.     This is the basis of the approach we have taken in PC
  118. NetHack; rather than using the somewhat idiosyncratic identifier
  119. "plenk", however, we have systematically employed (in the files
  120. trampoli.h and trampoli.c) identifiers generated by appending
  121. underscores to the ends of the names of the functions we have needed
  122. to so indirect(1).
  123.  
  124. There are a few small complications. The first is ensuring that both
  125. the versions of the trampoli'd function (foo() and foo_()) are
  126. similarly typed by the appropriate extern declarations (which
  127. themselves must be written); this can be accomplished by placing all
  128. of these declarations in a header file that is processed _twice_,
  129. once before and once after the inclusion of the file containing the
  130. trampoli macro definitions, thereby ensuring that both variants of
  131. the name have been seen in connection with the appropriate types. The
  132. second is that some care must be exercised not to employ other macros
  133. that interfere with the normal recognition of function syntax: it is
  134. the presence of the open parenthesis after the name of the function
  135. that triggers name substitution, and not the fact that the function
  136. is called; and so (particularly in the case of declarations) it is
  137. necessary that if a macro is used to supply the _arguments_ of a
  138. trampoli'd function, it must also supply the name (this necessity in
  139. fact triggered a change in the style of the macros that provide
  140. dialect-independent function declaration in NetHack; the new style
  141. would have you write FDECL(functionName, (argTypes...)).
  142.     Finally, there is the case of functions declared to take no
  143. arguments whatsoever; in Standard C this is notated:
  144.  
  145.     some_t aFunction(void);
  146.  
  147. for no theoretically well-motivated reason I can discern. Such a
  148. declaration will _not_ match a macro definition such as
  149.  
  150.     #define aFunction() aFunction_()
  151.  
  152. -- in fact the compiler will detect an error when processing that
  153. declaration in the scope of this macro. The only solution is to
  154. eschew the use of this strange syntax and unfrabjously forgo the
  155. concomitant security of well- and thoroughly- checked typage. To
  156. which end we have provided an ecchy macro, NDECL(functionName), which
  157. uses the new syntax _unless_ the compiler is not Standard or OVERLAY
  158. is enabled.
  159.  
  160. There is one further consideration: that this technique only applies,
  161. of course, to functions that are published to the linker. For this
  162. reason, wherever such trampoli'd functions were originally declared
  163. static, that declaration has been changed to "STATIC_PTR", a macro
  164. that expands to "static" unless the OVERLAY flag has been selected in
  165. the configuration file, enabling the trampoli mechanism. Thus such
  166. functions lose their privacy in this one case.
  167.  
  168.  
  169. 3. OVLx
  170. -------
  171. The strategies described above work fine, but they only stretch so
  172. far. In particular, they do not admit of an overlay structure in
  173. which functions are linked into different overlays even though they
  174. originate in the same source file.
  175.     Classically, this is not considered a real limitation,
  176. because one has the freedom to regroup the functions into different
  177. source files as needed; however, in the case of NetHack this was not
  178. a realistic option, since what structure this unwieldy programme has
  179. is precisely in the current grouping of functions together.
  180. Nonetheless, the ability to perform some functional grouping is an
  181. absolute requirement for acceptable performance, since many NetHack
  182. source modules (were.c, for example) contain one or two tiny
  183. functions that are called with great frequency (several millions of
  184. times per game is not unheard of) and whose return value determines
  185. whether the remaining large, slow functions of the file will be
  186. required at all in the near future. Obviously these small checking
  187. functions should be linked into the same overlays with their callers,
  188. while the remainder of the source module should not.
  189.  
  190. In order to make this possible we ran a dynamic profile on the game
  191. to determine exactly which functions in which modules required such
  192. distinguished treatment, and we have flagged each function for
  193. conditional compilation (with #if ... #endif) in groups according
  194. approximately to their frequency of invocation and functionality.
  195. These groups have been arbitrarily named in each source file (in
  196. decreasing order of frequency), OVL0, OVL1, OVL2, OVL3 and OVLB (B
  197. for "base functions", those that deserve no special treatment at
  198. all). It is thus possible to compile only a small number of the
  199. functions in a file by defining but one or two of these symbols on
  200. the compiler's command line (with the switch /DOVL2, for example);
  201. the compiler will ignore the remainder as if they did not exist.
  202. (There is an "escape clause" in hack.h that ensures that if none of
  203. these flags is defined on the command line, then all of them will be
  204. during compilation; this makes the non-use of this mechanism
  205. straightforward!)
  206.     By repeated invocation of the compiler on the _same_ source
  207. file it is possible to accumulate disjoint object modules that
  208. between them contain the images of all the functions in the original
  209. source, but partitioned as is most convenient. Care must, of course,
  210. be taken over conflicts of name in both the object file put out (all
  211. slices will by default be called SRCFILE.OBJ, and this default must
  212. be overridden with distinct file names for each output slice) and in
  213. the names of the text segments the compiler is to generate; you can
  214. see this at work in Makefile.ovl. (You may wonder, as we did at
  215. first, why the text segment name would have to be made distinct in
  216. each object file slice (the default segment name is a function of the
  217. source file name and the compilation model only). The reason for this
  218. is, quite daftly to my mind, that the linker considers the identity
  219. of segment names and combine classes better reason to combine
  220. segments than the programmer's explicit instructions in the requested
  221. overlay pattern are reason to keep them apart. Programmer, ask not
  222. why...).
  223.  
  224. Once again, that works fine except for the small matter of
  225. declarations (where have we heard this before?). For objects that
  226. once were static must now be made visible to the linker that they may
  227. be resolved across the reaches of inter-overlay space. To this end we
  228. have provided three macros, all of which expand simply to "static" if
  229. no OVLx flags are defined on the compilation command line. They are:
  230.  
  231. STATIC_DCL    which introduces a declaration (as distinct from a
  232.         definition) of an object that would be static were it
  233.         not for the requirements of the OVLx mechanism. Its
  234.         expansion is "static", normally, but it becomes
  235.         "extern" in the event that this source file has been
  236.         split into slices with the OVLx mechanism.
  237.  
  238. STATIC_OVL    is used when _defining_ a function (giving its text,
  239.         that is) that is logically static but may be called
  240.         across slices; it expands to "static" unless OVLx is
  241.         active; in the latter case it expands to null,
  242.         leaving the function with "previous linkage" as the
  243.         standard says. Note that this behaviour is quite
  244.         similar to, but very different from, that of
  245.         STATIC_PTR (described above), which has the same two
  246.         expansions but which is triggered not by OVLx but by
  247.         the OVERLAY flag which enables the trampoli mechanism.
  248.             STATIC_OVL also differs from the STATIC_DCL
  249.         and STATIC_VAR in that it is employed _within_ OVLx
  250.         slices, while the others are used to generate
  251.         declarations and are deployed in areas common to all
  252.         slices.
  253.  
  254. STATIC_VAR    is used to introduce uninitialised would-be-static
  255.         variables. Its expansion is complex, since it must
  256.         read as "static" in the usual case, but as "extern"
  257.         if OVLx is in use -- in all overlays but one, where
  258.         it must expand to the null sequence -- giving it
  259.         "previous linkage" and "tentative definition" (to
  260.         ensure that the variable gets defined at all).
  261.             This one took a while to get right, and
  262.         believe me, using the macro is a lot easier than
  263.         trying to keep the #ifdefs straight yourself!
  264.  
  265. An initialised variable that is file-level static unless OVLx is in
  266. use must now be written with a STATIC_DCL declaration, and a
  267. definition (and static initialiser) enclosed within the bracketing
  268. tag of one of the OVLx slices (any will do; we use OVLB).
  269.     Type definitions, macro definitions and extern declarations
  270. should, of course remain outside any OVLx slice.
  271.  
  272. Finally, of course, objects whose visibility need not be extended may
  273. safely continue to be declared static. And in this case, at least,
  274. the compiler will provide diagnostics that inform you when an object
  275. has slipped through the cracks and requires the application of Magic
  276. Macro Salve.
  277.  
  278. It is perhaps less than obvious that when a function is _both_ called
  279. across an OVLx split and referenced through a pointer, it should be
  280. treated as a pointered function (that is, it should get trampoli
  281. entries and should be defined STATIC_PTR). The reason for this is that
  282. the STATIC_xxx macros associated with OVLx _only_ change the
  283. declaration patterns of the objects, while trampoli results in the
  284. generation of necessary code.
  285.     It is correct to do this, because the declarations produced by
  286. STATIC_PTR are triggered by OVERLAY's being defined, and the selection
  287. of OVERLAY is an absolute precondition for the activation of OVLx.
  288.  
  289.  
  290. 4. Hacking
  291. ----------
  292. Before undertaking any serious modifications to the overlay structure
  293. or support mechanisms, you should know that a _lot_ of work has gone
  294. into the current scheme. If performance seems poor, remember: the
  295. overlay manager itself can be invoked up to ten thousand times in a
  296. second, and although the space available for loading overlays (once
  297. the data and stack spaces have been accounted for) is less than half
  298. the total size of the overlays that are swapped through it, a disk
  299. access occurs well under 0.1% of the time(2). Furthermore, this
  300. performance (such as it is) has been achieved without substantive
  301. change or restructuring of the NetHack source code, which must remain
  302. portable to many platforms other than the PC.
  303.  
  304. If these observations do not daunt you, you are a true Bit Warrior
  305. indeed (or aspiration anyway), and we await your comments with bait.
  306.  
  307. ------------------------------------------------------------------------
  308.  
  309. NOTES:
  310. ------
  311.  
  312. (1) In fact, we have applied this technique throughout NetHack, even
  313.     in cases where it is not strictly necessary (since the pointered
  314.     calls are not across overlay splits, for example - though note
  315.     that there are more splits than might be initially apparent, due
  316.     to the effects of the OVLx hackage as described in section 3).
  317.     There is, however, one exception; and beware: it is an exception
  318.     with fangs. The file termcap.c contains a few pointered functions
  319.     that we decided _not_ to trampoli for performance reasons (screen
  320.     output is one of the problem areas on the PC port at the moment,
  321.     in terms of performance). It is therefore vital to the health of
  322.     PC NetHack as it currently stands that the OVLx slice termcap.0 be
  323.     linked into the ROOT "overlay".
  324.  
  325. (2) These figures are for a 4.77 MHz PC-XT running in low memory with
  326.     an older version of both the overlay manager and the NetHack
  327.     overlay arrangement. On a more capable computer and with the
  328.     current software, the figures are probably more like a 100kHz peak
  329.     service rate and a hit rate (since we fixed the bug in the LRU
  330.     clock logic!) in excess of 99.99% -- hopefully not both at the
  331.     same time.
  332.  
  333. ------------------------------------------------------------------------
  334. Stephen P Spackman                             stephen@tira.uchicago.edu
  335. ------------------------------------------------------------------------
  336.                  * Hack On! *
  337.